﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace MobileMP.TcpEssentials
{
    public class TcpConnection : IDisposable
    {
	    public TcpClient Client { get; set; }
		public NetworkStream Stream { get; set; }
		public string IP
		{
			get { return _ip; }
		}
		public int Port
		{
			get { return _port; }
		}

		private readonly string _ip;
		private readonly int _port;
	    private readonly bool _isListening;

		public TcpConnection(string ip, int port, bool isListening)
		{
			_isListening = isListening;
			_ip = ip;
			_port = port;
		}

	    private void VerifyConnection()
	    {
			if (Stream == null) 
				throw new InvalidOperationException("The connection has not been initialized using method 'Connect' or 'ConnectAsync'.");
	    }

	    public void Connect()
	    {
			if (_isListening)
			{
				var listen = new TcpListener(IPAddress.Parse(IP), Port);
				listen.Start();
				Client = listen.AcceptTcpClient();
				listen.Stop();
			}
			else
				Client = new TcpClient(IP, Port);

			Stream = Client.GetStream();
	    }

	    public async Task ConnectAsync()
	    {
			TcpClient cli;
			if (_isListening)
			{
				var listen = new TcpListener(IPAddress.Parse(IP), Port);
				listen.Start();
				cli = await listen.AcceptTcpClientAsync();
				listen.Stop();
			}
			else
				cli = new TcpClient(IP, Port);

		    Stream = cli.GetStream();
	    }

		public void WriteData(byte[] data, int offset, int count)
		{
			VerifyConnection();

			byte[] header = BitConverter.GetBytes(count);
			Stream.Write(header, 0, header.Length);
			Stream.Write(data, offset, count);
			Stream.Flush();
		}

		public async Task WriteDataAsync(byte[] data, int offset, int count)
		{
			VerifyConnection();

			byte[] header = BitConverter.GetBytes(count);
			await Stream.WriteAsync(header, 0, header.Length);
			await Stream.WriteAsync(data, offset, count);
			await Stream.FlushAsync();
		}

		public byte[] ReadData(int buffersize)
		{
			VerifyConnection();

			int byteLength;

			using (var ms = new MemoryStream())
			{
				while (ms.Length != 4)
				{
					byte[] buffer = new byte[4];
					int read = Stream.Read(buffer, 0, buffer.Length);

					ms.Write(buffer, 0, read);
				}
				byteLength = BitConverter.ToInt32(ms.ToArray(), 0);
			}

			using (var ms = new MemoryStream())
			{
				while (ms.Length != byteLength)
				{
					byte[] buffer = new byte[buffersize];
					int read = Stream.Read(buffer, 0, buffersize);

					ms.Write(buffer, 0, read);
				}
				return ms.ToArray();
			}
		}

		public async Task<byte[]> ReadDataAsync(int buffersize)
		{
			VerifyConnection();

			int byteLength;
			using (var ms = new MemoryStream())
			{
				while (ms.Length != 4)
				{
					byte[] buffer = new byte[4];
					int read = await Stream.ReadAsync(buffer, 0, buffer.Length);

					await ms.WriteAsync(buffer, 0, read);
				}
				byteLength = BitConverter.ToInt32(ms.ToArray(), 0);
			}

			using (var ms = new MemoryStream())
			{
				while (ms.Length != byteLength)
				{
					byte[] buffer = new byte[buffersize];
					int read = await Stream.ReadAsync(buffer, 0, buffersize);

					await ms.WriteAsync(buffer, 0, read);
				}
				return ms.ToArray();
			}
		}

		public void ReadDataToFile(string path, int buffersize)
		{
			VerifyConnection();

			int byteLength;
			using (var ms = new MemoryStream())
			{
				while (ms.Length != 4)
				{
					byte[] buffer = new byte[4];
					int read = Stream.Read(buffer, 0, buffer.Length);

					ms.Write(buffer, 0, read);
				}
				byteLength = BitConverter.ToInt32(ms.ToArray(), 0);
			}

			using (var fs = new FileStream(path, FileMode.Create))
			{
				int count = 0;
				while (count != byteLength)
				{
					byte[] buffer = new byte[buffersize];
					int read = Stream.Read(buffer, 0, buffersize);

					fs.Write(buffer, 0, read);
					count += read;
				}
			}
		}

		public async Task ReadDataToFileAsync(string path, int buffersize)
		{
			VerifyConnection();

			int byteLength;
			using (var ms = new MemoryStream())
			{
				while (ms.Length != 4)
				{
					byte[] buffer = new byte[4];
					int read = await Stream.ReadAsync(buffer, 0, buffer.Length);

					await ms.WriteAsync(buffer, 0, read);
				}
				byteLength = BitConverter.ToInt32(ms.ToArray(), 0);
			}

			using (var fs = new FileStream(path, FileMode.Create))
			{
				int count = 0;
				while (count != byteLength)
				{
					byte[] buffer = new byte[buffersize];
					int read = await Stream.ReadAsync(buffer, 0, buffersize);

					await fs.WriteAsync(buffer, 0, read);
					count += read;
				}	
			}
		}

		public void Dispose()
		{
			Stream.Dispose();
			Client.Close();
		}
	}
}